package com.stray.crawl.worker.task;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import com.stray.crawl.Constants.DefaultSetting;
import com.stray.crawl.Constants.HttpCtxKey;
import com.stray.crawl.BeanContext;
import com.stray.crawl.ResultContext;
import com.stray.crawl.TaskContext;
import com.stray.crawl.expr.ExpressExecutor;
import com.stray.crawl.supt.EventPublisher;
import com.stray.crawl.worker.HttpEnums;
import com.stray.crawl.worker.HttpEnums.HttpHeaderKey;
import com.stray.crawl.worker.HttpEnums.HttpHeaderValue;
import com.stray.crawl.worker.HttpEnums.RequestMethod;
import com.stray.crawl.worker.TaskStatus;

public class CrawlTask implements Callable<ResultContext> {

	protected HttpClient client = BeanContext.get(HttpClient.class);
	protected ExpressExecutor executor = BeanContext.get(ExpressExecutor.class);

	protected EventPublisher<CrawlTask, TaskStatus> publisher;
	protected TaskContext taskContext;
	protected ResultContext resultContext;

	protected CrawlTask() {
		super();
	}

	public CrawlTask(EventPublisher<CrawlTask, TaskStatus> publisher, TaskContext taskContext) {
		this.publisher = publisher;
		this.taskContext = taskContext;
		this.resultContext =  new ResultContext();
	}

	public TaskContext getTaskContext() {
		return taskContext;
	}

	public ResultContext getResultContext() {
		return resultContext;
	}

	protected Object convertContent(HttpEntity entity) throws IOException {
		String charset = getCharset(entity);
		String content = EntityUtils.toString(entity, charset);

		return content;
	}

	@SuppressWarnings("unchecked")
	protected void doConnect() throws Exception {
		HttpRequestBase req = null;
		HttpEntity entity = null;

		try {
			HttpContext httpCtx = new BasicHttpContext();
			req = newHttpRequest();
			HttpResponse resp = client.execute(req, httpCtx);
			entity = resp.getEntity();

			Object content = convertContent(entity);

			resultContext.setStatusCode(resp.getStatusLine().getStatusCode());
			resultContext.setRequestSummary((String) httpCtx.getAttribute(HttpCtxKey.REQUEST_SUMMARY));
			resultContext.setRequestHeaders((Map<String, String>) httpCtx.getAttribute(HttpCtxKey.REQUEST_HEADERS));
			resultContext.setResponseSummary((String) httpCtx.getAttribute(HttpCtxKey.RESPONSE_SUMMARY));
			resultContext.setResponseHeaders((Map<String, String>) httpCtx.getAttribute(HttpCtxKey.RESPONSE_HEADERS));

			resultContext.setContent(content);
		} finally {
			EntityUtils.consumeQuietly(entity);
			releaseConnectionQuietly(req);
		}
	}

	private void doParse() {
		List<Object[]> results = new ArrayList<Object[]>();

		String obj = (String) resultContext.getContent();
		String express = taskContext.getExpress();

		List<?> elements = (List<?>) executor.executeQuery(taskContext, resultContext, obj, express);

		int fldCnt = taskContext.getFields().length;
		String[] queries = taskContext.getQueries();
		for (Object element : elements) {
			Object[] objs = new Object[fldCnt];

			for (int j = 0; j < fldCnt; j++) {
				String psr = queries[j];
				objs[j] = executor.executeQuery(taskContext, resultContext, element, psr);
			}

			results.add(objs);
		}

		resultContext.setElements(elements);
		resultContext.setResults(results);
	}

	protected void doAction() throws Exception {
		String qry = taskContext.getAction();
		Object obj = resultContext.getContent();

		executor.executeQuery(taskContext, resultContext, obj, qry);
	}

	@Override
	public ResultContext call() throws Exception {
		try {
			long timestamp = System.currentTimeMillis();
			resultContext.setStartTimestamp(timestamp);

			publisher.onEventRaise(this, TaskStatus.CONNECT);
			doConnect();
			resultContext.setConnectElapsedTime((int) (System.currentTimeMillis() - timestamp));

			publisher.onEventRaise(this, TaskStatus.PARSE);
			timestamp = System.currentTimeMillis();
			doParse();
			resultContext.setParseElapsedTime((int) (System.currentTimeMillis() - timestamp));

			publisher.onEventRaise(this, TaskStatus.PROCESS);
			timestamp = System.currentTimeMillis();
			doAction();
			resultContext.setProcessElapsedTime((int) (System.currentTimeMillis() - timestamp));
		} finally {
			publisher.onEventRaise(this, TaskStatus.COMPLETE);
			resultContext.setFinishTimestamp(System.currentTimeMillis());

			publisher.onCallback(this);
		}

		return resultContext;
	}

	protected String getCharset(HttpEntity entity) {
		String charset;

		if (taskContext.isCharsetExist()) {
			charset = taskContext.getCharset();
		} else {
			ContentType contentType = ContentType.getOrDefault(entity);
			Charset c = contentType.getCharset();

			charset = c != null ? c.toString() : DefaultSetting.CHARSET;
		}

		return charset;
	}

	private HttpRequestBase newHttpRequest() throws URISyntaxException {
		HttpRequestBase req;
		URL url = taskContext.getUrl();

		if (HttpEnums.getRequestMethod(taskContext.getMethod()) == RequestMethod.post) {
			req = new HttpPost(url.toURI());
		} else {
			req = new HttpGet(url.toURI());
		}

		HttpParams params = req.getParams();
		if (taskContext.isCharsetExist()) {
			HttpProtocolParams.setContentCharset(params, taskContext.getCharset());
		}
		if (taskContext.isTimeoutExist()) {
			HttpConnectionParams.setConnectionTimeout(params, taskContext.getTimeout());
		}

		req.setHeader(HttpHeaderKey.HOST, HttpEnums.getHostHeader(url));
		req.setHeader(HttpHeaderKey.ACCEPT, HttpEnums.getAcceptHeader(url).getValue());

		req.setHeader(HttpHeaderKey.CONNECTION, HttpHeaderValue.CONNECTION_KEY_KEEP_ALIVE);
		String acceptLang = taskContext.isParamNotEmpty(HttpHeaderKey.ACCEPT_LANGUAGE) ? taskContext.getParam(HttpHeaderKey.ACCEPT_LANGUAGE) : DefaultSetting.ACCEPT_LANGUAGE;
		req.setHeader(HttpHeaderKey.ACCEPT_LANGUAGE, acceptLang);

		if (taskContext.isUseReferer()) {
			if (taskContext.isParamNotEmpty(HttpHeaderKey.REFERER)) {
				req.setHeader(HttpHeaderKey.REFERER, taskContext.getParam(HttpHeaderKey.REFERER));
			}
		}

		if (taskContext.isUseCache()) {
			if (taskContext.isParamNotEmpty(HttpHeaderKey.CACHE_CONTROL)) {
				req.setHeader(HttpHeaderKey.CACHE_CONTROL, taskContext.getParam(HttpHeaderKey.CACHE_CONTROL));
			}
			if (taskContext.isParamNotEmpty(HttpHeaderKey.IF_MODIFIED_SINCE)) {
				req.setHeader(HttpHeaderKey.IF_MODIFIED_SINCE, taskContext.getParam(HttpHeaderKey.IF_MODIFIED_SINCE));
			}
			if (taskContext.isParamNotEmpty(HttpHeaderKey.IF_NONE_MATCH)) {
				req.setHeader(HttpHeaderKey.IF_NONE_MATCH, taskContext.getParam(HttpHeaderKey.IF_NONE_MATCH));
			}
		} else {
			req.setHeader(HttpHeaderKey.CACHE_CONTROL, HttpHeaderValue.NO_CACHE);
			req.setHeader(HttpHeaderKey.PRAGMA, HttpHeaderValue.NO_CACHE);
		}

		return req;
	}

	private void releaseConnectionQuietly(HttpRequestBase req) {
		if (req != null) {
			try {
				req.releaseConnection();
			} catch (Exception e) {
				// ignore
			}
		}
	}

}